package com.qwe.graduation.shiro.config;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.qwe.graduation.entity.User;
import com.qwe.graduation.service.IUserService;
import com.qwe.graduation.shiro.utils.JwtToken;
import com.qwe.graduation.shiro.utils.JwtUtil;
import com.qwe.graduation.shiro.utils.SpringContextUtils;
import com.qwe.graduation.utils.CommonConstant;
import com.qwe.graduation.utils.RedisUtil;
import com.qwe.graduation.utils.exception.Constants;
import com.qwe.graduation.utils.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Set;


/**
 * @author lzf
 * @date 2022/10/28 9:36
 * @description ShiroRealm
 */
@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    @Lazy
    private IUserService IUserService;

    @Autowired
    @Lazy
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    @Lazy
    private RedisUtil redisUtil;



    /**
     * 必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 授权(验证权限时调用)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        System.out.println("111");
        return info;
    }


    /**
     * 用户信息认证是在用户进行登录的时候进行验证(不存redis)
     * 也就是说验证用户输入的账号和密码是否正确，错误抛出异常
     *
     * @param auth 用户登录的账号密码信息
     * @return 返回封装了用户信息的 AuthenticationInfo 实例
     * @throws AuthenticationException
     */
//    @Override
//    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
//        log.debug("===============Shiro身份认证开始============doGetAuthenticationInfo==========");
//        String token = (String) auth.getCredentials();
//        if (token == null) {
//            HttpServletRequest req = SpringContextUtils.getHttpServletRequest();
//            log.info("————————身份认证失败——————————IP地址:  "+ "，URL:"+req.getRequestURI());
//            throw new AuthenticationException("token为空!");
//        }
//        // 校验token有效性
//        User loginUser = null;
//        try {
//            loginUser = this.checkUserTokenIsEffect(token);
//        } catch (AuthenticationException e) {
//            JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),"401",e.getMessage());
//            e.printStackTrace();
//            return null;
//        }
//        return new SimpleAuthenticationInfo(loginUser, token, getName());
//    }

//    /**
//     * 校验token的有效性
//     *
//     * @param token
//     */
//    public User checkUserTokenIsEffect(String token) throws AuthenticationException {
//        // 解密获得username，用于和数据库进行对比
////        String username = JwtUtil.getUsername(token);
////        String orgId = JwtUtil.getOrgId(token);
////        if (username == null) {
////            throw new AuthenticationException("token非法无效!");
////        }
//
//        // 查询用户信息
////        log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token);
////        LoginUser loginUser = TokenUtils.getLoginUser(username,commonApi,redisUtil);
////        //LoginUser loginUser = commonApi.getUserByName(username);
////        if (loginUser == null) {
////            throw new AuthenticationException("用户不存在!");
////        }
////        // 判断用户状态
////        if (loginUser.getStatus() != 1) {
////            throw new AuthenticationException("账号已被锁定,请联系管理员!");
////        }
////        // 校验token是否超时失效 & 或者账号密码是否错误
////        if (!jwtTokenRefresh(token, username, orgId, loginUser.getPassword())) {
////            throw new AuthenticationException(CommonConstant.TOKEN_IS_INVALID_MSG);
////        }
//        //update-begin-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
////        String userTenantIds = loginUser.getRelTenantIds();
////        if(oConvertUtils.isNotEmpty(userTenantIds)){
////            String contextTenantId = TenantContext.getTenant();
////            String str ="0";
////            if(oConvertUtils.isNotEmpty(contextTenantId) && !str.equals(contextTenantId)){
////                //update-begin-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
////                String[] arr = userTenantIds.split(",");
////                if(!oConvertUtils.isIn(contextTenantId, arr)){
////                    throw new AuthenticationException("用户租户信息变更,请重新登陆!");
////                }
////                //update-end-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
////            }
////        }
//        //update-end-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
//        return null;
//    }


    /**
     * 认证(登录时调用)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)  {
        String accessToken = (String) token.getPrincipal();
        if (accessToken == null || accessToken=="") {
            //todo 这里可以改成我自己定义的异常，然后抛出去给前端
            throw new AuthenticationException("token为空,请重新登录！");
        }
        // 校验token有效性
        User tokenEntity =null;
        try {
            tokenEntity = this.checkUserTokenIsEffect(accessToken);
        }catch (AuthenticationException e) {
            JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),"401",e.getMessage());
            e.printStackTrace();
            return null;
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(tokenEntity, accessToken, getName());
        return info;
    }
    /**
     * 校验token的有效性
     *  springboot2.3.+新增了一个配置项server.error.includeMessage，默认是NEVER，
     *  因此默认是不是输出message的，只要开启就可以了,否则无法拿到shiro抛出异常信息message
     * @param token
     */
    public User checkUserTokenIsEffect(String token) throws AuthenticationException {
        // 解密获得username，用于和数据库进行对比
        String userId = JwtUtil.getUserId(token);
        if (userId == null) {
//            throw new ServiceException(Constants.CODE_401,"token无效，请重新登录");
            throw new AuthenticationException("token无效，请重新登录");
        }

        // 查询用户信息
        User loginUser = IUserService.getById(userId);
        if (loginUser == null) {
//            throw new ServiceException(Constants.CODE_401,"找不到用户");
            throw new AuthenticationException("找不到用户");
        }
        //判断用户token是否失效
        String redisToken = null;
        redisToken = "prefix_user_token_"+token;
        boolean b = redisUtil.hasKey(redisToken);
        if (b==false){
            throw new AuthenticationException("token无效，请重新登录");
        }
        // 判断用户状态
//        if (loginUser.getStatus() != 0) {
//            throw new LockedAccountException(CommonCode.WEB_ACCOUNT_LOCKED.getMessage());
//        }
        // 校验token是否超时失效 & 或者账号密码是否错误
//        if (!jwtTokenRefresh(token, userId, loginUser.getUserPhone())) {
//            throw new IncorrectCredentialsException(CommonCode.WEB_TOKEN_FAILURE.getMessage());
//        }
        return loginUser;
    }



    /**
     * JWTToken刷新生命周期 （实现： 用户在线操作不掉线功能）
     * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样)，缓存有效期设置为Jwt有效时间的2倍
     * 2、当该用户再次请求时，通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
     * 3、当该用户这次请求jwt生成的token值已经超时，但该token对应cache中的k还是存在，则表示该用户一直在操作只是JWT的token失效了，程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值，该缓存生命周期重新计算
     * 4、当该用户这次请求jwt在生成的token值已经超时，并在cache中不存在对应的k，则表示该用户账户空闲超时，返回用户信息已失效，请重新登录。
     * 注意： 前端请求Header中设置Authorization保持不变，校验有效性以缓存中的token为准。
     * 用户过期时间 = Jwt有效时间 * 2。
     *
     * @param userId
     * @param userPhone
     * @return
     */
    public boolean jwtTokenRefresh(String token, String userId, String userPhone) {
        //如果缓存中的token为空，直接返回失效异常
        String cacheToken = stringRedisTemplate.opsForValue().get(CommonConstant.PREFIX_USER_TOKEN + token);
        if (!StringUtils.isBlank(cacheToken)) {
            // 校验token有效性
            if (!JwtUtil.verify(cacheToken, userId, userPhone)) {
                JwtUtil.sign(userId, userPhone);
            }
            return true;
        }
        return false;
    }

    /**
     * 清除当前用户的权限认证缓存
     *
     * @param principals 权限信息
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }
}